<?php
// +----------------------------------------------------------------------
// | INPHP
// | Copyright (c) 2023 https://inphp.cc All rights reserved.
// | Author: 幺月儿(https://gitee.com/lulanyin) Email: inphp@qq.com
// | 该文件的开源协议以所在项目的LICENSE文件为准，请遵守开源协议要求
// +----------------------------------------------------------------------
// | 
// +----------------------------------------------------------------------
namespace app\install\http;

use Inphp\Core\Config;
use Inphp\Core\Modules;
use Inphp\Core\Service;
use Inphp\Core\Util\Str;

class index
{
    public function index()
    {
        //判断是否已安装过
        $env = Config::get("env");
        $env = is_array($env) ? $env : [];
        $installed = !isset($env["installed"]) || $env["installed"] != false;
        //安装锁定文件
        $lockFile = ROOT."/public/install.lock";
        $installed = $installed || is_file($lockFile);
        $step = GET("step");
        $step = $step == 1 || $step == 2 ? $step : 0;
        viewAssign("step", $step);
        viewAssign("installed", $installed);
        //当前域名
        viewAssign("host", getHost());
        $post = getClient()->post;
        if (!$installed && !empty($post)) {
            viewAssign("step", 3);
            //处理安装参数
            $username = $post["username"] ?? null;
            $username = !empty($username) ? Str::trim($username) : null;
            if (empty($username)) {
                $this->showMessage("缺少超级管理员用户名");
                return;
            }
            if (!Str::isLUN($username)) {
                $this->showMessage("用户名格式无效，仅限于英文字母开头，中间可添加数字、下划线，且不可以下划线结尾");
                return;
            }
            if (strlen($username) < 6 || strlen($username) > 16) {
                $this->showMessage("用户名格式无效，最少填写6个字符，且不可超过16个字符");
                return;
            }
            $password = POST("password");
            $password = !empty($password) ? trim($password) : null;
            if (empty($password)) {
                $this->showMessage("缺少管理员登陆密码");
                return;
            }
            if (strlen($password) < 6 || strlen($password) > 16) {
                $this->showMessage("登陆密码无效，最少填写6个字符，且不可超过16个字符");
                return;
            }

            $adminConfig = Modules::getModule("admin")->getConfig();
            $pwdKey = Str::randomString(6);
            $adminConfig["auth"] = [
                "username"  => $username,
                "password"  => md5(md5($password).$pwdKey),
                "pwdKey"    => $pwdKey,
                "lifeTime" => 3600
            ];

            //redis
            $redis_host = POST("redis_host");
            $redis_host = !empty($redis_host) ? Str::trim($redis_host) : "127.0.0.1";
            $redis_port = POST("redis_port");
            $redis_port = is_numeric($redis_port) && $redis_port > 0 && $redis_port <= 65533 ? ceil($redis_port) : 6379;
            $redis_password = POST("redis_password");
            $redis_password = !empty($redis_password) ? trim($redis_password) : "";
            //测试连接
            $redis = new \Redis();
            if (!@$redis->connect($redis_host, $redis_port, 3)) {
                $this->showMessage("Redis连接失败，请检查配置是否正确！");
                return;
            }
            if (!empty($redis_password)) {
                if(!$redis->auth($redis_password)) {
                    $this->showMessage("Redis密码错误，请检查配置是否正确！");
                    return;
                }
            }

            //配置文件
            $contents = [];
            $contents[] = "<?php";
            $contents[] = "// +----------------------------------------------------------------------";
            $contents[] = "// | INPHP";
            $contents[] = "// | Copyright (c) 2023 https://inphp.cc All rights reserved.";
            $contents[] = "// | Licensed ( https://opensource.org/licenses/MIT )";
            $contents[] = "// | Author: 幺月儿(https://gitee.com/lulanyin) Email: inphp@qq.com";
            $contents[] = "// +----------------------------------------------------------------------";
            $contents[] = "// | 数据库、redis 配置文件 安装日期：".date("Y-m-d H:i:s");
            $contents[] = "// +----------------------------------------------------------------------";
            $contents[] = "return [";
            $contents[] = '    "pdo" => [';
            $contents[] = '        //是否打开调式模式，会在控制台输出某些内容';
            $contents[] = '        "debug"          => false,';
            $contents[] = '        //数据库类型(当前内置的链式操作、语法处理等仅支持mysql，其它类型数据库均未测试!!!!!!)';
            $contents[] = '        "driver"         => "mysql",';
            $contents[] = '        //默认遍历方式';
            $contents[] = '        "fetchModel"     => \PDO::FETCH_ASSOC,';
            $contents[] = '        //表名前缀，为了方便使用，读写分离的前缀必须统一';
            $contents[] = '        "tablePrefix"    => "%s",';
            $contents[] = '        //超时';
            $contents[] = '        "timeout"        => 3,';
            $contents[] = '        //允许重连次数';
            $contents[] = '        "reconnectTimes" => 5,';
            $contents[] = '        //字符集';
            $contents[] = '        "charset"        => "utf8mb4",';
            $contents[] = '        //日志文件位置';
            $contents[] = '        "errorLogFile"   => RUNTIME."/db/error.txt",';
            $contents[] = '        //执行日志保存的目录，所有 insert, update, delete 都会保存到该文件夹，如果不保存，请填写 null/false，按天保存文件';
            $contents[] = '        "executeLogDir"  => RUNTIME."/db/execute",';
            $contents[] = '        //保留关键字';
            $contents[] = '        "keywords"       => [';
            $contents[] = '            //可自定义过滤特殊字段为：`字段`';
            $contents[] = '        ],';
            $contents[] = '        //默认连接，若使用读写分离，可以不定义';
            $contents[] = '        "default" => [';
            $contents[] = '            //服务器地址';
            $contents[] = '            "host"      => "%s",';
            $contents[] = '            //端口';
            $contents[] = '            "port"      => %u,';
            $contents[] = '            //数据库用户名';
            $contents[] = '            "username"  => "%s",';
            $contents[] = '            //密码';
            $contents[] = '            "password"  => "%s",';
            $contents[] = '            //数据库名';
            $contents[] = '            "dbname"    => "%s",';
            $contents[] = '            //选项';
            $contents[] = '            "options"   => [';
            $contents[] = '                //mysql选项，防乱码';
            $contents[] = '                \PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES \'utf8mb4\';",';
            $contents[] = '                //使用缓冲区';
            $contents[] = '                \PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true,';
            $contents[] = '                //为了兼容不同的数据服务端配置，数据统一输出为字符串，客户端数据判断时，请注意使用 === 还是 ==';
            $contents[] = '                \PDO::ATTR_STRINGIFY_FETCHES => true';
            $contents[] = '            ]';
            $contents[] = '        ],';
            $contents[] = '        //读写分离可自定义，同步需要自行处理';
            $contents[] = '        //"read" => [],';
            $contents[] = '        //"write" => [],';
            $contents[] = '        "pools"         => 20';
            $contents[] = '    ],';
            $contents[] = '    //redis 缓存';
            $contents[] = '    "redis" => [';
            $contents[] = '        //服务器地址';
            $contents[] = '        "host"           => "%s",';
            $contents[] = '        //端口';
            $contents[] = '        "port"           => %u,';
            $contents[] = '        //密码';
            $contents[] = '        "password"       => "%s",';
            $contents[] = '        //选择库';
            $contents[] = '        "select"         => 0,';
            $contents[] = '        //超时';
            $contents[] = '        "timeout"        => 3,';
            $contents[] = '        //读超时时间';
            $contents[] = '        "readTimeout"    => 3,';
            $contents[] = '        //缓存过期时间，-1为永不过期';
            $contents[] = '        "expire"         => 86400,';
            $contents[] = '        //重试间隔';
            $contents[] = '        "retryInterval"  => 0,';
            $contents[] = '        //缓存名前缀';
            $contents[] = '        "prefix"         => "%s",';
            $contents[] = '        //连接池数量';
            $contents[] = '        "pools"          => 64';
            $contents[] = '    ]';
            $contents[] = "];";

            $var1_db_tablePrefix = "pre_";
            $var2_db_host = "127.0.0.1";
            $var3_db_port = 3306;
            $var4_db_username = "root";
            $var5_db_password = "12345678";
            $var6_db_dbname = "inphp";

            $var7_redis_host = $redis_host;
            $var8_redis_port = $redis_port;
            $var9_redis_password = $redis_password;
            $var10_redis_prefix = Str::randomString(4)."_";

            //数据库
            $database = POST("database");
            $database = !empty($database);
            if ($database) {
                //数据库配置
                $db_host = POST("db_host");
                $db_host = !empty($db_host) ? Str::trim($db_host) : "127.0.0.1";
                $db_port = POST("db_port");
                $db_port = is_numeric($db_port) && $db_port > 0 && $db_port <= 65533 ? ceil($db_port) : 3306;
                $db_username = POST("db_username");
                $db_username = !empty($db_username) ? Str::trim($db_username) : "root";
                if (empty($db_username)) {
                    $this->showMessage("缺少数据库用户名");
                    return;
                }
                $db_password = POST("db_password");
                $db_password = !empty($db_password) ? trim($db_password) : "12345678";
                if (empty($db_password)) {
                    $this->showMessage("缺少数据库用户名");
                    return;
                }
                $db_name = POST("db_name");
                $db_name = !empty($db_name) ? Str::trim($db_name) : "inphp";
                $db_prefix = POST("db_prefix");
                $db_prefix = !empty($db_prefix) ? Str::trim($db_prefix) : "";
                //测试数据库连接
                $connect = @mysqli_connect($db_host, $db_username, $db_password, "", $db_port);
                if (!$connect) {
                    $this->showMessage("数据库连接失败，请返回检查配置是否正确！");
                    return;
                }
                //检测数据库是否存在
                $sql = 'SELECT COUNT(*) AS `exists` FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMATA.SCHEMA_NAME="'.$db_name.'"';
                $res = @mysqli_query($connect, $sql);
                if (!$res) {
                    $this->showMessage("无法查询数据库");
                    return;
                }
                $row = $res->fetch_object();
                $sql = ($row->exists > 0 ? "ALTER" : "CREATE")." DATABASE `{$db_name}` DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci;";
                if (!@mysqli_query($connect, $sql)) {
                    $this->showMessage("数据库创建失败，请稍检查配置是否正确！");
                    return;
                }
                //是否有数据库需要恢复
                $file = ROOT."/install.sql";
                if (is_file($file)) {
                    $restore = POST("restore");
                    if ($restore == 1) {
                        $content = file_get_contents($file);
                        if (!Service::isCLI()) {
                            //为了能完整的导入数据库...
                            set_time_limit(0);
                        }
                        //处理掉一些字符
                        $sqlArray = explode("\n", $content);
                        $strings = [];
                        foreach ($sqlArray as $str) {
                            if (stripos($str, "#") !== 0 && stripos($str, "/*") !== 0 && !empty($str)) {
                                $strings[] = $str;
                            }
                        }
                        @mysqli_query($connect, "use {$db_name};");
                        $sqlList = explode(";\n", join("\n", $strings));
                        $success = $fail = 0;
                        foreach ($sqlList as $sql) {
                            $sql = str_replace("DROP TABLE IF EXISTS `@table_prefix_", "DROP TABLE IF EXISTS `{$db_prefix}", $sql);
                            $sql = str_replace("CREATE TABLE `@table_prefix_", "CREATE TABLE `{$db_prefix}", $sql);
                            $sql = str_replace("INSERT INTO `@table_prefix_", "INSERT INTO `{$db_prefix}", $sql);
                            if (@mysqli_query($connect, $sql)) {
                                $success++;
                            } else {
                                $fail++;
                            }
                        }
                        viewAssign("restoreSuccess", $success > 0);
                        viewAssign("sqlSuccess", $success);
                        viewAssign("sqlFail", $fail);
                    }
                }
                //关闭连接
                @mysqli_close($connect);
                //赋值
                $var1_db_tablePrefix = $db_prefix;
                $var2_db_host = $db_host;
                $var3_db_port = $db_port;
                $var4_db_username = $db_username;
                $var5_db_password = $db_password;
                $var6_db_dbname = $db_name;
            }
            $contents = join("\r\n", $contents);
            $contents = sprintf($contents, $var1_db_tablePrefix, $var2_db_host, $var3_db_port, $var4_db_username, $var5_db_password, $var6_db_dbname, $var7_redis_host, $var8_redis_port, $var9_redis_password, $var10_redis_prefix);
            if (!Config::save("private/db.php", $contents)) {
                $this->showMessage("无法保存数据库配置文件，请检查文件夹权限！");
            }
            if (!Modules::getModule("admin")->saveConfig("config", $adminConfig)) {
                $this->showMessage("无法保存超管账号配置，请检查文件夹权限！");
                return;
            }
            $envFile = [];
            $envFile[] = "<?php";
            $envFile[] = "return [";
            $envFile[] = '    //打开开发模式';
            $envFile[] = '    "devMode" => false,';
            $envFile[] = '    //系统是否已安装完成';
            $envFile[] = '    "installed" => true';
            $envFile[] = "];";
            @Config::save("env.php", join("\r\n", $envFile));
            //保存一个安装锁定文件
            @file_put_contents(ROOT."/public/install.lock", date("Y-m-d H:i:s"));
            $this->showMessage("恭喜您，软件已全部配置完成，祝您学习进步、前程似锦、开心快乐！", true);
            return;
        }
        if ($step == 0) {
            //阅读许可协议

        } elseif ($step == 1) {
            //环境检测
            $client = getClient();
            $server = [
                "host"      => getHost(false),
                "name"      => Service::isCLI() ? "swoole高性能PHP协程框架" : ($client->server["SERVER_NAME"] ?? $client->server["server_name"]),
                "version"   => phpversion(),
                "server"    => Service::isCLI() ? "swoole高性能PHP协程框架" : ($client->server["SERVER_SOFTWARE"] ?? $client->server["server_software"]),
                "root"      => ROOT
            ];
            viewAssign("server", $server);
            //版本
            $env = [
                "version"   => !(version_compare(PHP_VERSION, '8.0.0') == -1),
                "pdo"       => extension_loaded('pdo') && extension_loaded('pdo_mysql'),
                "mysqli"    => extension_loaded('mysqli'),
                "curl"      => extension_loaded('curl') && function_exists('curl_init'),
                "gd"        => extension_loaded('gd'),
                "fileinfo"  => extension_loaded('fileinfo'),
                "redis"     => extension_loaded('redis')
            ];
            viewAssign("env", $env);
            //读写
            $rw = true;
            $path = [
                "", "public", "runtime", "configs", "app"
            ];
            $list = [];
            foreach ($path as $p) {
                $read = is_readable(ROOT."/{$p}");
                $write = is_writable(ROOT."/{$p}");
                $list[] = [
                    "name"      => $p,
                    "read"      => $read,
                    "write"     => $write
                ];
                if (!$read || !$write) {
                    $rw = false;
                }
            }
            viewAssign("dirList", $list);
            viewAssign("rw", $rw);
        } elseif ($step == 2) {
            //检测是否有数据库需要安装
            $file = ROOT."/install.sql";
            viewAssign("sql", is_file($file));
        }
    }

    private function showMessage(string $message = "恭喜您，软件已全部配置成功。", bool $success = false) {
        viewAssign("success", $success);
        viewAssign("title", $success ? "安装成功" : "安装失败");
        viewAssign("message", $message);
    }
}